package ez

import (
	"context"
	"fmt"
	"gitee.com/dreamwood/ez-go/tools"
	"go.mongodb.org/mongo-driver/event"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
	"net"
	"strings"
	"sync"
	"time"
)

var mgoLogCache mongodbLogCache

type mongodbLogCache map[int64]*mongodbLogCacheItem

type mongodbLogCacheItem struct {
	Ctx   context.Context
	Cost  int64
	Query string
	State string
}

var mongoDbLk sync.Mutex

func init() {
	mgoLogCache = make(mongodbLogCache)
	mongoDbLk = sync.Mutex{}
}
func addMongoLogOnStart(requestId int64, ctx context.Context, query string) {
	mongoDbLk.Lock()
	defer mongoDbLk.Unlock()
	mgoLogCache[requestId] = &mongodbLogCacheItem{
		Ctx:   ctx,
		Query: query,
		State: "START",
	}
}
func addMongoLogOnEnd(requestId int64, timeCost float64, state string) {
	mongoDbLk.Lock()
	cmd := mgoLogCache[requestId].Query
	if ConfigCore.RequestLogOpen {
		session, ok := mgoLogCache[requestId].Ctx.Value("session").(*Session)
		if ok {
			session.AddSessionLog(
				SesionLogTye_Mongodb,
				fmt.Sprintf("T:%.2f ms", timeCost),
				cmd,
				fmt.Sprintf("ReqId:%v", requestId))
		}
	}
	delete(mgoLogCache, requestId)
	mongoDbLk.Unlock()
	ConfigMongo.Logger.Write(fmt.Sprintf(
		"%s [%s] %.2f ms %s",
		time.Now().Format("2006-01-02 15:04:05"),
		state,
		timeCost,
		cmd,
	))

}

type MongoDbConfig struct {
	User           string   `yaml:"user"`
	Pass           string   `yaml:"pass"`
	Hosts          []string `yaml:"hosts"`
	Auth           string   `yaml:"auth"`
	Db             string   `yaml:"db"`
	LogOn          bool     `yaml:"log-on"`
	LogTo          string   `yaml:"log-to"`
	Logger         EzLogger
	PoolSize       uint64 `yaml:"pool-size"`
	BackUpOpen     bool   `yaml:"auto-backup"`
	BackUpInterval int64  `yaml:"backup-interval"`
}

func (this *MongoDbConfig) UseDefaultLogger() {
	switch this.LogTo {
	case "Nsq":
		LoadNsqConfig()
		InitNsq()
		log := NewNsqLogger()
		log.NsqProducer = NsqProducer
		log.Topic = "ez.mongo"
		log.TraceDepth = 9
		log.DumpDepth = 0
		this.Logger = log
	case "File":
		//InitFileLogger()
		log := NewFileLogger()
		log.TraceDepth = 9
		log.DumpDepth = 0
		this.Logger = log
	default:
		this.Logger = LogConsole
	}
}

func LoadMongoDbConfig() *MongoDbConfig {
	ConfigMongo = new(MongoDbConfig)
	tools.CreateConfigFromYml("./app.yaml", "mongodb", &ConfigMongo)
	ConfigMongo.UseDefaultLogger()
	return ConfigMongo
}

func InitMongoDb() {

	LogToConsoleNoTrace("MONGODB 开始初始化")

	uri := fmt.Sprintf(
		"mongodb://%s:%s@%s/%s?authSource=%s&",
		ConfigMongo.User, ConfigMongo.Pass,
		strings.Join(ConfigMongo.Hosts, ","),
		ConfigMongo.Db, ConfigMongo.Auth)
	LogToConsoleNoTrace(fmt.Sprintf("数据库链接URI：%s", uri))
	//事件监听输出日志
	m := new(event.CommandMonitor)
	m.Started = func(ctx context.Context, startedEvent *event.CommandStartedEvent) {
		if ConfigMongo.LogOn {
			addMongoLogOnStart(startedEvent.RequestID, ctx, startedEvent.Command.String())
		}
	}
	m.Succeeded = func(ctx context.Context, succeededEvent *event.CommandSucceededEvent) {

		timeCost := float64(succeededEvent.DurationNanos) / 1000000
		if ConfigMongo.LogOn {
			//调用成功的日志
			addMongoLogOnEnd(succeededEvent.RequestID, timeCost, "SUCCESS")
		}
	}
	m.Failed = func(ctx context.Context, failEvent *event.CommandFailedEvent) {
		timeCost := float64(failEvent.DurationNanos) / 1000000
		addMongoLogOnEnd(failEvent.RequestID, timeCost, "FAILED")
	}
	dialer := &net.Dialer{
		KeepAlive: 30 * time.Second,
	}
	client, err := mongo.NewClient(
		options.Client().
			ApplyURI(uri).
			SetMaxPoolSize(ConfigMongo.PoolSize).
			SetLocalThreshold(3 * time.Second).
			SetMonitor(m).
			SetDialer(dialer).
			SetConnectTimeout(10 * time.Second).
			SetServerSelectionTimeout(5 * time.Second),
	)
	if err != nil {
		LogToConsole(fmt.Sprintf("初始化mongodb失败:[NewClient]%s", err.Error()))
	}
	err = client.Connect(context.Background())
	if err != nil {
		LogToConsole(fmt.Sprintf("初始化mongodb失败:[Connect]%s", err.Error()))
	}
	ctxTimeout, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()
	err = client.Ping(ctxTimeout, nil)
	if err != nil {
		LogToConsole(fmt.Sprintf("初始化mongodb失败:[Ping]%s", err.Error()))
	}
	DBMongo = client.Database(ConfigMongo.Db)
	LogToConsoleNoTrace("MONGODB 初始化完成")
}

// Note 通常该情况使用默认的数据库连接，有时需要同时连接多个数据库，
// 此时需要使用 NewMongoDbConfig 和 NewMongoDb 方法

func NewMongoDbConfig(filePath, node string) *MongoDbConfig {
	conf := new(MongoDbConfig)
	tools.CreateConfigFromYml(filePath, node, &conf)
	ConfigMongo.UseDefaultLogger()
	return ConfigMongo
}

func NewMongoDb(conf *MongoDbConfig) *mongo.Database {
	LogToConsoleNoTrace("MONGODB 开始初始化")
	uri := fmt.Sprintf(
		"mongodb://%s:%s@%s/%s?authSource=%s&",
		conf.User, conf.Pass,
		strings.Join(conf.Hosts, ","),
		conf.Db, conf.Auth)
	LogToConsoleNoTrace(fmt.Sprintf("数据库链接URI：%s", uri))
	//事件监听输出日志
	m := new(event.CommandMonitor)
	m.Started = func(ctx context.Context, startedEvent *event.CommandStartedEvent) {
		if ConfigMongo.LogOn {
			addMongoLogOnStart(startedEvent.RequestID, ctx, startedEvent.Command.String())
		}
	}
	m.Succeeded = func(ctx context.Context, succeededEvent *event.CommandSucceededEvent) {

		timeCost := float64(succeededEvent.DurationNanos) / 1000000
		if ConfigMongo.LogOn {
			//调用成功的日志
			addMongoLogOnEnd(succeededEvent.RequestID, timeCost, "SUCCESS")
		}
	}
	m.Failed = func(ctx context.Context, failEvent *event.CommandFailedEvent) {
		timeCost := float64(failEvent.DurationNanos) / 1000000
		addMongoLogOnEnd(failEvent.RequestID, timeCost, "FAILED")
	}

	client, err := mongo.NewClient(
		options.Client().ApplyURI(uri).SetMaxPoolSize(conf.PoolSize).SetMonitor(m))
	if err != nil {
		LogToConsole(fmt.Sprintf("初始化mongodb失败:[NewClient]%s", err.Error()))
	}

	err = client.Connect(context.Background())
	if err != nil {
		LogToConsole(fmt.Sprintf("初始化mongodb失败:[Connect]%s", err.Error()))
	}

	ctxTimeout, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()
	err = client.Ping(ctxTimeout, nil)
	if err != nil {
		LogToConsole(fmt.Sprintf("初始化mongodb失败:[Ping]%s", err.Error()))
	}

	LogToConsoleNoTrace("MONGODB 初始化完成")
	return client.Database(conf.Db)
}

func MongoMark(mark ...string) {
	ConfigMongo.Logger.Write(Mark())
	if len(mark) > 0 {
		ConfigMongo.Logger.Write(fmt.Sprintf("%s", strings.Join(mark, " ")))
	}
}
